/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
    ArkMethod,
    ArkClass,
    ArkFile,
    ArkAliasTypeDefineStmt
} from "arkanalyzer";
import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger';
import {BaseChecker, BaseMetaData} from "../BaseChecker";
import {Defects, IssueReport} from "../../model/Defects";
import {Rule} from "../../model/Rule";
import {ClassMatcher, MatcherCallback, MatcherTypes} from "../../matcher/Matchers";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ForeachArgsCheck');
const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: "docs/no-unnecessary-type-constraint-check.md",
    description: "Disallow unnecessary constraints on generic types"
};

export class NoUnnecessaryTypeConstraintCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private clsMatcher: ClassMatcher = {
        matcherType: MatcherTypes.CLASS,
    };

    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.clsMatcher,
            callback: this.check
        }
        return [matchBuildCb];
    }

    public check = (targetClass: ArkClass) => {
        const severity = this.rule.alert ?? this.metaData.severity;
        if (this.getFileExtension(targetClass.getDeclaringArkFile().getName(), 'ts')) {
            if (targetClass.getCode()) {
                let genericDeclaration = this.getGenericDeclaration(targetClass.getCode() ?? '');
                if (genericDeclaration) {
                    this.reportIssue(targetClass.getDeclaringArkFile(), targetClass.getLine() ?? 0, targetClass.getColumn() ?? 0, genericDeclaration, severity);
                }
            }
            for (let method of targetClass.getMethods()) {
                this.processArkMethod(method, severity)
            }
        }
    }

    public processArkMethod(method: ArkMethod, severity: number) {
        if (method.getCode()) {
            let genericDeclaration = this.getGenericDeclaration(method.getCode() ?? '');
            if (genericDeclaration) {
                if (method.getColumn() !== null && method.getLine() !== null) {
                    this.reportIssue(method.getDeclaringArkFile(), method.getLine() ?? 0, method.getColumn() ?? 0, genericDeclaration, severity);
                }
            }
        }
        for (let aliasType of method.getBody()?.getAliasTypeMap() ?? []) {
            aliasType.forEach((generic) => {
                if (generic instanceof Array) {
                    generic.forEach((genericType) => {
                        if (genericType instanceof ArkAliasTypeDefineStmt) {
                            let genericDeclaration = this.getGenericDeclaration(genericType.getOriginalText() ?? '');
                            if (genericDeclaration) {
                                this.reportIssue(method.getDeclaringArkFile(), genericType.getOriginPositionInfo().getLineNo() ?? 0, genericType.getOriginPositionInfo().getColNo() ?? 0, genericDeclaration, severity);
                            }
                        }
                    })
                }
            })
        }
        for (let stmt of method.getBody()?.getCfg()?.getStmts() ?? []) {
            if (stmt.getOriginalText()) {
                let genericDeclaration = this.getGenericDeclaration(stmt.getOriginalText() ?? '');
                if (genericDeclaration) {
                    this.reportIssue(method.getDeclaringArkFile(), stmt.getOriginPositionInfo().getLineNo() ?? 0, stmt.getOriginPositionInfo().getColNo() ?? 0, genericDeclaration, severity);
                }
            }
        }
    }

    private getFileExtension(filePath: string, filetype: string): boolean {
        const match = filePath.match(/\.([0-9a-zA-Z]+)$/);
        if (match) {
            const extension = match[1];
            return extension === filetype;
        }
        return false;
    }

    public getGenericDeclaration(str: string) {
        let match = str.match(/^[^({]*/);
        let result = match ? match[0] : str;
        const regex = /<\w+\s+extends\s+(any|unknown)>/;
        return regex.test(result) ? result : '';
    }

    private reportIssue(arkFile: ArkFile, lineNum: number, colum: number, text: string, severity: number) {
        if (arkFile) {
            let startColum = colum + text.indexOf('<') + 1;
            let endColum = colum + text.indexOf('>');
            const filePath = arkFile.getFilePath();
            let defect = new Defects(lineNum, startColum, endColum, this.metaData.description, severity, this.rule.ruleId, filePath,
                this.metaData.ruleDocPath, true, false, false);
            this.issues.push(new IssueReport(defect, undefined));
        } else {
            logger.debug('originStmt or arkFile is null');
        }
    }
}